// Copyright Epic Games, Inc. All Rights Reserved.

#include "LyraExperienceManagerComponent.h"
#include "Engine/World.h"
#include "Net/UnrealNetwork.h"
#include "LyraExperienceDefinition.h"
#include "LyraExperienceActionSet.h"
#include "LyraExperienceManager.h"
#include "GameFeaturesSubsystem.h"
#include "System/LyraAssetManager.h"
#include "GameFeatureAction.h"
#include "GameFeaturesSubsystemSettings.h"
#include "TimerManager.h"
#include "Settings/LyraSettingsLocal.h"
#include "LyraLogChannels.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraExperienceManagerComponent)

//@TODO: 异步加载经验定义本身
//@TODO: 显式处理失败（进入"完成但失败"状态而不是check()）
//@TODO: 在适当的时间执行动作阶段而不是一次性执行
//@TODO: 支持停用经验并执行卸载动作
//@TODO: 思考停用/清理对预加载资源的意义
//@TODO: 处理停用游戏功能，目前我们"泄漏"它们为启用状态
//（对于从经验移动到经验的客户端，我们实际上想要区分需求并只卸载一些，而不是为它们卸载所有内容然后立即重新加载）
//@TODO: 处理内置和基于URL的插件（搜索冒号？）

namespace LyraConsoleVariables
{
	static float ExperienceLoadRandomDelayMin = 0.0f; // 经验加载最小随机延迟
	static FAutoConsoleVariableRef CVarExperienceLoadRandomDelayMin(
		TEXT("lyra.chaos.ExperienceDelayLoad.MinSecs"),
		ExperienceLoadRandomDelayMin,
		TEXT("此值（以秒为单位）将作为经验加载完成的延迟（与随机值lyra.chaos.ExperienceDelayLoad.RandomSecs一起添加）"),
		ECVF_Default);

	static float ExperienceLoadRandomDelayRange = 0.0f; // 经验加载随机延迟范围
	static FAutoConsoleVariableRef CVarExperienceLoadRandomDelayRange(
		TEXT("lyra.chaos.ExperienceDelayLoad.RandomSecs"),
		ExperienceLoadRandomDelayRange,
		TEXT("0到此值之间的随机时间（以秒为单位）将作为经验加载完成的延迟（与固定值lyra.chaos.ExperienceDelayLoad.MinSecs一起添加）"),
		ECVF_Default);

	/**
	 * 获取经验加载延迟持续时间
	 * @return 延迟时间（秒）
	 */
	float GetExperienceLoadDelayDuration()
	{
		return FMath::Max(0.0f, ExperienceLoadRandomDelayMin + FMath::FRand() * ExperienceLoadRandomDelayRange); // 计算总延迟时间
	}
}

/**
 * 构造函数，设置组件默认复制
 */
ULyraExperienceManagerComponent::ULyraExperienceManagerComponent(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	SetIsReplicatedByDefault(true); // 默认启用复制
}

/**
 * 设置当前经验
 * @param ExperienceId 经验资源ID
 */
void ULyraExperienceManagerComponent::SetCurrentExperience(FPrimaryAssetId ExperienceId)
{
	ULyraAssetManager& AssetManager = ULyraAssetManager::Get(); // 获取资源管理器
	FSoftObjectPath AssetPath = AssetManager.GetPrimaryAssetPath(ExperienceId); // 获取资源路径
	TSubclassOf<ULyraExperienceDefinition> AssetClass = Cast<UClass>(AssetPath.TryLoad()); // 尝试加载资源类
	check(AssetClass); // 确保类有效
	const ULyraExperienceDefinition* Experience = GetDefault<ULyraExperienceDefinition>(AssetClass); // 获取经验默认对象

	check(Experience != nullptr); // 确保经验有效
	check(CurrentExperience == nullptr); // 确保当前经验为空
	CurrentExperience = Experience; // 设置当前经验
	StartExperienceLoad(); // 开始经验加载
}

/**
 * 注册或立即调用高优先级经验加载委托
 * @param Delegate 要注册的委托
 */
void ULyraExperienceManagerComponent::CallOrRegister_OnExperienceLoaded_HighPriority(FOnLyraExperienceLoaded::FDelegate&& Delegate)
{
	if (IsExperienceLoaded()) // 如果经验已加载
	{
		Delegate.Execute(CurrentExperience); // 立即执行委托
	}
	else
	{
		OnExperienceLoaded_HighPriority.Add(MoveTemp(Delegate)); // 注册委托
	}
}

/**
 * 注册或立即调用普通优先级经验加载委托
 * @param Delegate 要注册的委托
 */
void ULyraExperienceManagerComponent::CallOrRegister_OnExperienceLoaded(FOnLyraExperienceLoaded::FDelegate&& Delegate)
{
	if (IsExperienceLoaded()) // 如果经验已加载
	{
		Delegate.Execute(CurrentExperience); // 立即执行委托
	}
	else
	{
		OnExperienceLoaded.Add(MoveTemp(Delegate)); // 注册委托
	}
}

/**
 * 注册或立即调用低优先级经验加载委托
 * @param Delegate 要注册的委托
 */
void ULyraExperienceManagerComponent::CallOrRegister_OnExperienceLoaded_LowPriority(FOnLyraExperienceLoaded::FDelegate&& Delegate)
{
	if (IsExperienceLoaded()) // 如果经验已加载
	{
		Delegate.Execute(CurrentExperience); // 立即执行委托
	}
	else
	{
		OnExperienceLoaded_LowPriority.Add(MoveTemp(Delegate)); // 注册委托
	}
}

/**
 * 获取当前经验（已加载状态下）
 * @return 当前经验定义
 */
const ULyraExperienceDefinition* ULyraExperienceManagerComponent::GetCurrentExperienceChecked() const
{
	check(LoadState == ELyraExperienceLoadState::Loaded); // 确保已加载状态
	check(CurrentExperience != nullptr); // 确保当前经验有效
	return CurrentExperience; // 返回当前经验
}

/**
 * 检查经验是否已加载
 * @return 是否已加载
 */
bool ULyraExperienceManagerComponent::IsExperienceLoaded() const
{
	return (LoadState == ELyraExperienceLoadState::Loaded) && (CurrentExperience != nullptr); // 检查状态和有效性
}

/**
 * 当前经验复制时的回调
 */
void ULyraExperienceManagerComponent::OnRep_CurrentExperience()
{
	StartExperienceLoad(); // 开始经验加载
}

/**
 * 开始经验加载过程
 */
void ULyraExperienceManagerComponent::StartExperienceLoad()
{
	check(CurrentExperience != nullptr); // 确保当前经验有效
	check(LoadState == ELyraExperienceLoadState::Unloaded); // 确保未加载状态

	UE_LOG(LogLyraExperience, Log, TEXT("EXPERIENCE: StartExperienceLoad(CurrentExperience = %s, %s)"),
		*CurrentExperience->GetPrimaryAssetId().ToString(), // 记录经验ID
		*GetClientServerContextString(this)); // 记录客户端服务器上下文

	LoadState = ELyraExperienceLoadState::Loading; // 设置状态为加载中

	ULyraAssetManager& AssetManager = ULyraAssetManager::Get(); // 获取资源管理器

	TSet<FPrimaryAssetId> BundleAssetList; // 资源包资源列表
	TSet<FSoftObjectPath> RawAssetList; // 原始资源列表

	BundleAssetList.Add(CurrentExperience->GetPrimaryAssetId()); // 添加当前经验
	for (const TObjectPtr<ULyraExperienceActionSet>& ActionSet : CurrentExperience->ActionSets) // 遍历动作集
	{
		if (ActionSet != nullptr) // 确保动作集有效
		{
			BundleAssetList.Add(ActionSet->GetPrimaryAssetId()); // 添加动作集资源
		}
	}

	// 加载与经验相关的资源

	TArray<FName> BundlesToLoad; // 要加载的资源包列表
	BundlesToLoad.Add(FLyraBundles::Equipped); // 添加装备资源包

	//@TODO: 将此客户端/服务器内容集中到LyraAssetManager中
	const ENetMode OwnerNetMode = GetOwner()->GetNetMode(); // 获取网络模式
	const bool bLoadClient = GIsEditor || (OwnerNetMode != NM_DedicatedServer); // 是否加载客户端
	const bool bLoadServer = GIsEditor || (OwnerNetMode != NM_Client); // 是否加载服务器
	if (bLoadClient) // 如果需要加载客户端
	{
		BundlesToLoad.Add(UGameFeaturesSubsystemSettings::LoadStateClient); // 添加客户端加载状态
	}
	if (bLoadServer) // 如果需要加载服务器
	{
		BundlesToLoad.Add(UGameFeaturesSubsystemSettings::LoadStateServer); // 添加服务器加载状态
	}

	TSharedPtr<FStreamableHandle> BundleLoadHandle = nullptr; // 资源包加载句柄
	if (BundleAssetList.Num() > 0) // 如果有资源包要加载
	{
		BundleLoadHandle = AssetManager.ChangeBundleStateForPrimaryAssets(BundleAssetList.Array(), BundlesToLoad, {}, false, FStreamableDelegate(), FStreamableManager::AsyncLoadHighPriority); // 更改资源包状态
	}

	TSharedPtr<FStreamableHandle> RawLoadHandle = nullptr; // 原始资源加载句柄
	if (RawAssetList.Num() > 0) // 如果有原始资源要加载
	{
		RawLoadHandle = AssetManager.LoadAssetList(RawAssetList.Array(), FStreamableDelegate(), FStreamableManager::AsyncLoadHighPriority, TEXT("StartExperienceLoad()")); // 加载资源列表
	}

	// 如果两个异步加载都在运行，合并它们
	TSharedPtr<FStreamableHandle> Handle = nullptr; // 合并加载句柄
	if (BundleLoadHandle.IsValid() && RawLoadHandle.IsValid()) // 如果两个句柄都有效
	{
		Handle = AssetManager.GetStreamableManager().CreateCombinedHandle({ BundleLoadHandle, RawLoadHandle }); // 创建合并句柄
	}
	else
	{
		Handle = BundleLoadHandle.IsValid() ? BundleLoadHandle : RawLoadHandle; // 使用有效的句柄
	}

	FStreamableDelegate OnAssetsLoadedDelegate = FStreamableDelegate::CreateUObject(this, &ThisClass::OnExperienceLoadComplete); // 资源加载完成委托
	if (!Handle.IsValid() || Handle->HasLoadCompleted()) // 如果句柄无效或已加载完成
	{
		// 资源已加载，立即调用委托
		FStreamableHandle::ExecuteDelegate(OnAssetsLoadedDelegate); // 执行委托
	}
	else
	{
		Handle->BindCompleteDelegate(OnAssetsLoadedDelegate); // 绑定完成委托

		Handle->BindCancelDelegate(FStreamableDelegate::CreateLambda([OnAssetsLoadedDelegate]() // 绑定取消委托
			{
				OnAssetsLoadedDelegate.ExecuteIfBound(); // 执行委托
			}));
	}

	// 这组资源会被预加载，但我们不会基于它阻塞经验的开始
	TSet<FPrimaryAssetId> PreloadAssetList; // 预加载资源列表
	//@TODO: 确定要预加载的资源（但不阻塞）
	if (PreloadAssetList.Num() > 0) // 如果有预加载资源
	{
		AssetManager.ChangeBundleStateForPrimaryAssets(PreloadAssetList.Array(), BundlesToLoad, {}); // 更改资源包状态
	}
}

/**
 * 经验资源加载完成回调
 */
void ULyraExperienceManagerComponent::OnExperienceLoadComplete()
{
	check(LoadState == ELyraExperienceLoadState::Loading); // 确保加载中状态
	check(CurrentExperience != nullptr); // 确保当前经验有效

	UE_LOG(LogLyraExperience, Log, TEXT("EXPERIENCE: OnExperienceLoadComplete(CurrentExperience = %s, %s)"),
		*CurrentExperience->GetPrimaryAssetId().ToString(), // 记录经验ID
		*GetClientServerContextString(this)); // 记录客户端服务器上下文

	// 查找我们的GameFeaturePlugins的URL - 过滤掉重复项和没有有效映射的项
	GameFeaturePluginURLs.Reset(); // 重置插件URL列表

	// 收集游戏功能插件URL的lambda函数
	auto CollectGameFeaturePluginURLs = [This=this](const UPrimaryDataAsset* Context, const TArray<FString>& FeaturePluginList)
	{
		for (const FString& PluginName : FeaturePluginList) // 遍历插件名称列表
		{
			FString PluginURL; // 插件URL
			if (UGameFeaturesSubsystem::Get().GetPluginURLByName(PluginName, /*out*/ PluginURL)) // 通过名称获取插件URL
			{
				This->GameFeaturePluginURLs.AddUnique(PluginURL); // 添加唯一插件URL
			}
			else
			{
				ensureMsgf(false, TEXT("OnExperienceLoadComplete failed to find plugin URL from PluginName %s for experience %s - fix data, ignoring for this run"), *PluginName, *Context->GetPrimaryAssetId().ToString()); // 确保失败，记录错误
			}
		}

		// 		// 添加我们的额外插件
		// 		if (!CurrentPlaylistData->GameFeaturePluginToActivateUntilDownloadedContentIsPresent.IsEmpty())
		// 		{
		// 			FString PluginURL;
		// 			if (UGameFeaturesSubsystem::Get().GetPluginURLByName(CurrentPlaylistData->GameFeaturePluginToActivateUntilDownloadedContentIsPresent, PluginURL))
		// 			{
		// 				GameFeaturePluginURLs.AddUnique(PluginURL);
		// 			}
		// 		}
	};

	CollectGameFeaturePluginURLs(CurrentExperience, CurrentExperience->GameFeaturesToEnable); // 收集经验的游戏功能插件
	for (const TObjectPtr<ULyraExperienceActionSet>& ActionSet : CurrentExperience->ActionSets) // 遍历动作集
	{
		if (ActionSet != nullptr) // 确保动作集有效
		{
			CollectGameFeaturePluginURLs(ActionSet, ActionSet->GameFeaturesToEnable); // 收集动作集的游戏功能插件
		}
	}

	// 加载并激活功能	
	NumGameFeaturePluginsLoading = GameFeaturePluginURLs.Num(); // 设置正在加载的插件数量
	if (NumGameFeaturePluginsLoading > 0) // 如果有插件要加载
	{
		LoadState = ELyraExperienceLoadState::LoadingGameFeatures; // 设置状态为加载游戏功能中
		for (const FString& PluginURL : GameFeaturePluginURLs) // 遍历插件URL
		{
			ULyraExperienceManager::NotifyOfPluginActivation(PluginURL); // 通知插件激活
			UGameFeaturesSubsystem::Get().LoadAndActivateGameFeaturePlugin(PluginURL, FGameFeaturePluginLoadComplete::CreateUObject(this, &ThisClass::OnGameFeaturePluginLoadComplete)); // 加载并激活游戏功能插件
		}
	}
	else // 没有插件要加载
	{
		OnExperienceFullLoadCompleted(); // 直接完成经验加载
	}
}

/**
 * 游戏功能插件加载完成回调
 * @param Result 加载结果
 */
void ULyraExperienceManagerComponent::OnGameFeaturePluginLoadComplete(const UE::GameFeatures::FResult& Result)
{
	// 减少正在加载的插件数量
	NumGameFeaturePluginsLoading--; // 递减计数

	if (NumGameFeaturePluginsLoading == 0) // 如果所有插件都加载完成
	{
		OnExperienceFullLoadCompleted(); // 完成经验加载
	}
}

/**
 * 经验完全加载完成回调
 */
void ULyraExperienceManagerComponent::OnExperienceFullLoadCompleted()
{
	check(LoadState != ELyraExperienceLoadState::Loaded); // 确保不是已加载状态

	// 插入随机延迟用于测试（如果配置了）
	if (LoadState != ELyraExperienceLoadState::LoadingChaosTestingDelay) // 如果不在混沌测试延迟状态
	{
		const float DelaySecs = LyraConsoleVariables::GetExperienceLoadDelayDuration(); // 获取延迟时间
		if (DelaySecs > 0.0f) // 如果有延迟
		{
			FTimerHandle DummyHandle; // 虚拟计时器句柄

			LoadState = ELyraExperienceLoadState::LoadingChaosTestingDelay; // 设置状态为混沌测试延迟中
			GetWorld()->GetTimerManager().SetTimer(DummyHandle, this, &ThisClass::OnExperienceFullLoadCompleted, DelaySecs, /*bLooping=*/ false); // 设置计时器

			return; // 返回，等待延迟结束
		}
	}

	LoadState = ELyraExperienceLoadState::ExecutingActions; // 设置状态为执行动作中

	// 执行动作
	FGameFeatureActivatingContext Context; // 游戏功能激活上下文

	// 如果设置了，只应用于我们的特定世界上下文
	const FWorldContext* ExistingWorldContext = GEngine->GetWorldContextFromWorld(GetWorld()); // 获取世界上下文
	if (ExistingWorldContext) // 如果存在世界上下文
	{
		Context.SetRequiredWorldContextHandle(ExistingWorldContext->ContextHandle); // 设置所需的世界上下文句柄
	}

	// 激活动作列表的lambda函数
	auto ActivateListOfActions = [&Context](const TArray<UGameFeatureAction*>& ActionList)
	{
		for (UGameFeatureAction* Action : ActionList) // 遍历动作列表
		{
			if (Action != nullptr) // 确保动作有效
			{
				//@TODO: 这些动作不接收世界在客户端-服务器PIE中可能有问题
				// 当前行为匹配游戏玩法标签等系统，其中加载和注册应用于整个进程，
				// 但实际将结果应用于角色仅限于特定世界
				Action->OnGameFeatureRegistering(); // 注册游戏功能
				Action->OnGameFeatureLoading(); // 加载游戏功能
				Action->OnGameFeatureActivating(Context); // 激活游戏功能
			}
		}
	};

	ActivateListOfActions(CurrentExperience->Actions); // 激活经验的动作
	for (const TObjectPtr<ULyraExperienceActionSet>& ActionSet : CurrentExperience->ActionSets) // 遍历动作集
	{
		if (ActionSet != nullptr) // 确保动作集有效
		{
			ActivateListOfActions(ActionSet->Actions); // 激活动作集的动作
		}
	}

	LoadState = ELyraExperienceLoadState::Loaded; // 设置状态为已加载

	OnExperienceLoaded_HighPriority.Broadcast(CurrentExperience); // 广播高优先级委托
	OnExperienceLoaded_HighPriority.Clear(); // 清空委托列表

	OnExperienceLoaded.Broadcast(CurrentExperience); // 广播普通优先级委托
	OnExperienceLoaded.Clear(); // 清空委托列表

	OnExperienceLoaded_LowPriority.Broadcast(CurrentExperience); // 广播低优先级委托
	OnExperienceLoaded_LowPriority.Clear(); // 清空委托列表

	// 应用任何必要的可伸缩性设置
#if !UE_SERVER // 如果不是服务器
	ULyraSettingsLocal::Get()->OnExperienceLoaded(); // 调用本地设置的经验加载回调
#endif
}

/**
 * 动作停用完成回调
 */
void ULyraExperienceManagerComponent::OnActionDeactivationCompleted()
{
	check(IsInGameThread()); // 确保在游戏线程中
	++NumObservedPausers; // 增加观察到的暂停者数量

	if (NumObservedPausers == NumExpectedPausers) // 如果所有预期的暂停者都已完成
	{
		OnAllActionsDeactivated(); // 调用所有动作停用完成
	}
}

/**
 * 获取生命周期复制属性
 * @param OutLifetimeProps 输出生命周期属性列表
 */
void ULyraExperienceManagerComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps); // 调用父类

	DOREPLIFETIME(ThisClass, CurrentExperience); // 复制当前经验
}

/**
 * Actor结束游戏时的处理
 * @param EndPlayReason 结束游戏原因
 */
void ULyraExperienceManagerComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	Super::EndPlay(EndPlayReason); // 调用父类

	// 停用此经验加载的任何功能
	//@TODO: 这也应该按照FILO处理
	for (const FString& PluginURL : GameFeaturePluginURLs) // 遍历插件URL
	{
		if (ULyraExperienceManager::RequestToDeactivatePlugin(PluginURL)) // 请求停用插件
		{
			UGameFeaturesSubsystem::Get().DeactivateGameFeaturePlugin(PluginURL); // 停用游戏功能插件
		}
	}

	//@TODO: 确保正确处理部分加载状态
	if (LoadState == ELyraExperienceLoadState::Loaded) // 如果已加载状态
	{
		LoadState = ELyraExperienceLoadState::Deactivating; // 设置状态为停用中

		// 确保如果有人注册为暂停者但立即触发，我们不会过早完成转换
		NumExpectedPausers = INDEX_NONE; // 重置预期暂停者数量
		NumObservedPausers = 0; // 重置观察到的暂停者数量

		// 停用并卸载动作
		FGameFeatureDeactivatingContext Context(TEXT(""), [this](FStringView) { this->OnActionDeactivationCompleted(); }); // 游戏功能停用上下文

		const FWorldContext* ExistingWorldContext = GEngine->GetWorldContextFromWorld(GetWorld()); // 获取世界上下文
		if (ExistingWorldContext) // 如果存在世界上下文
		{
			Context.SetRequiredWorldContextHandle(ExistingWorldContext->ContextHandle); // 设置所需的世界上下文句柄
		}

		// 停用动作列表的lambda函数
		auto DeactivateListOfActions = [&Context](const TArray<UGameFeatureAction*>& ActionList)
		{
			for (UGameFeatureAction* Action : ActionList) // 遍历动作列表
			{
				if (Action) // 确保动作有效
				{
					Action->OnGameFeatureDeactivating(Context); // 停用游戏功能
					Action->OnGameFeatureUnregistering(); // 取消注册游戏功能
				}
			}
		};

		DeactivateListOfActions(CurrentExperience->Actions); // 停用经验的动作
		for (const TObjectPtr<ULyraExperienceActionSet>& ActionSet : CurrentExperience->ActionSets) // 遍历动作集
		{
			if (ActionSet != nullptr) // 确保动作集有效
			{
				DeactivateListOfActions(ActionSet->Actions); // 停用动作集的动作
			}
		}

		NumExpectedPausers = Context.GetNumPausers(); // 获取预期的暂停者数量

		if (NumExpectedPausers > 0) // 如果有预期的暂停者
		{
			UE_LOG(LogLyraExperience, Error, TEXT("Actions that have asynchronous deactivation aren't fully supported yet in Lyra experiences")); // 记录错误
		}

		if (NumExpectedPausers == NumObservedPausers) // 如果所有预期的暂停者都已完成
		{
			OnAllActionsDeactivated(); // 调用所有动作停用完成
		}
	}
}

/**
 * 是否应显示加载屏幕
 * @param OutReason 输出原因
 * @return 是否应显示加载屏幕
 */
bool ULyraExperienceManagerComponent::ShouldShowLoadingScreen(FString& OutReason) const
{
	if (LoadState != ELyraExperienceLoadState::Loaded) // 如果经验未加载
	{
		OutReason = TEXT("Experience still loading"); // 设置原因为经验仍在加载
		return true; // 应显示加载屏幕
	}
	else
	{
		return false; // 不应显示加载屏幕
	}
}

/**
 * 所有动作停用完成回调
 */
void ULyraExperienceManagerComponent::OnAllActionsDeactivated()
{
	//@TODO: 我们实际上只是停用而没有完全卸载...
	LoadState = ELyraExperienceLoadState::Unloaded; // 设置状态为未加载
	CurrentExperience = nullptr; // 清空当前经验
	//@TODO:	GEngine->ForceGarbageCollection(true); // 强制垃圾回收
}